﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Threading;
using Xceed.Silverlight.Data.Stats;
using Xceed.Silverlight.Compatibility;
using System.Threading;

namespace Xceed.Silverlight.Data
{
  public abstract class DataSourceProvider : INotifyDataSourceChanged
  {
    #region Static Fields

    private static readonly Guid ExecuteCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteDataQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryPrerequisiteFunctionsCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteNativeStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteExceptionCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal DataSourceProvider()
    {
    }

    #endregion

    #region PageSize Property

    public int PageSize
    {
      get
      {
        return m_pageSize;
      }

      set
      {
        if( value < 0 )
          throw new ArgumentOutOfRangeException( "PageSize", "PageSize must be greater than or equal to zero." );

        m_pageSize = value;
      }
    }

    private int m_pageSize; //0

    #endregion

    #region CanFilter Property

    public virtual bool CanFilter
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public virtual bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public virtual bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public virtual bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanSkip Property

    public virtual bool CanSkip
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public virtual bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public virtual bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public virtual bool CanCalculateStats
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region ElementType Property

    protected internal abstract Type ElementType
    {
      get;
    }

    #endregion

    #region CanSort Internal Property

    internal virtual bool CanSort
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region IsAsynchronous Internal Property

    internal abstract bool IsAsynchronous
    {
      get;
    }

    #endregion

    #region IsCachingAllowed Internal Property

    internal abstract bool IsCachingAllowed
    {
      get;
    }

    #endregion

    #region IsBufferingAllowed Internal Property

    internal abstract bool IsBufferingAllowed
    {
      get;
    }

    #endregion

    #region IsQueryLengthLimited Internal Property

    internal virtual bool IsQueryLengthLimited
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region IsPrimaryKeyVolatile Internal Property

    internal virtual bool IsPrimaryKeyVolatile
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanFilterPredicate Internal Property

    internal virtual bool CanFilterPredicate
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToDeletedItem Internal Property

    internal virtual bool CanJumpToDeletedItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region PrimaryKeyInfo Internal Property

    internal IEnumerable<SortDescription> PrimaryKeyInfo
    {
      get
      {
        if( m_primaryKeyInfo == null )
        {
          m_primaryKeyInfo = new List<SortDescription>( 3 );

          IEnumerable<SortDescription> primaryKeyList = this.GetPrimaryKeyInfo();

          if( primaryKeyList != null )
          {
            m_primaryKeyInfo.AddRange( primaryKeyList );
          }
        }

        return m_primaryKeyInfo;
      }
    }

    private List<SortDescription> m_primaryKeyInfo; //null

    #endregion

    #region Dispatcher Internal Property

    internal Dispatcher Dispatcher
    {
      get
      {
        return m_dispatcher;
      }
    }

    private Dispatcher m_dispatcher; //null

    #endregion

    #region UnhandledException Event

    public event EventHandler<UnhandledDataExceptionEventArgs> UnhandledException;

    internal void HandleAsyncException( Exception e )
    {
      Debug.Assert( false, "An exception has been thrown" );

      Action a = () =>
      {
        throw e;
      };

      m_synchonizationContext.InvokeAction( a );
    }

    private void OnUnhandledException( UnhandledDataExceptionEventArgs e )
    {
      m_synchonizationContext.InvokeAction<UnhandledDataExceptionEventArgs>( this.RaiseUnhandledException, e );
    }

    private void RaiseUnhandledException( UnhandledDataExceptionEventArgs e )
    {
      if( this.UnhandledException != null )
      {
        this.UnhandledException( this, e );
      }
    }

    private ErrorResultAction HandleException( Exception exception, int retryCount )
    {
      UnhandledDataExceptionEventArgs e = new UnhandledDataExceptionEventArgs(
                                            exception,
                                            ErrorResultAction.Abort,
                                            retryCount );

      this.OnUnhandledException( e );

      return e.Result;
    }

    #endregion

    #region DataSourceChanged Internal Event

    event EventHandler<DataSourceChangedEventArgs> INotifyDataSourceChanged.DataSourceChanged
    {
      add
      {
        m_dataSourceChanged += value;
      }
      remove
      {
        m_dataSourceChanged -= value;
      }
    }

    protected void OnDataSourceChanged( DataSourceChangedEventArgs e )
    {
      m_synchonizationContext.InvokeAction<DataSourceChangedEventArgs>( this.RaiseDataSourceChanged, e );
    }

    private void RaiseDataSourceChanged( DataSourceChangedEventArgs e )
    {
      EventHandler<DataSourceChangedEventArgs> handler = m_dataSourceChanged;

      if( handler == null )
        return;

      DataSourceChangedEventArgs newEventArgs = this.WrapDataObjectEventArgs( e );
      bool isRelevant = ( newEventArgs.Action == DataSourceChangedAction.Reset )
                      || ( newEventArgs.Action == DataSourceChangedAction.Add && newEventArgs.NewItems.Count > 0 )
                      || ( newEventArgs.Action == DataSourceChangedAction.Remove && newEventArgs.OldItems.Count > 0 )
                      || ( newEventArgs.Action == DataSourceChangedAction.Replace && newEventArgs.NewItems.Count > 0 )
                      || ( newEventArgs.Action == DataSourceChangedAction.ItemChanged && newEventArgs.ItemsChanged.Count > 0 );

      // Make sure the event args has some relevant information before raising the event.
      // For instance, it is useless to raise an "Add" if no item has been added.
      if( isRelevant )
      {
        IList<object> newItems = newEventArgs.NewItems;
        IList<object> oldItems = newEventArgs.OldItems;
        bool oneToOneMapping = ( newItems.Count == oldItems.Count );

        // Mark each item removed has deleted to prevent any further ItemJumpRequest on it.
        for( int i = 0; i < oldItems.Count; i++ )
        {
          bool markAsRemoved = true;

          if( oneToOneMapping )
          {
            // On a replace action, the old item may have been moved to another location.
            // We must not tag it as removed if it is still in the source.
            markAsRemoved = !newItems.Contains( oldItems[ i ] );
          }

          if( markAsRemoved )
          {
            ( ( RawDataItem )oldItems[ i ] ).Delete();
          }
        }

        handler.Invoke( this, newEventArgs );
      }
    }

    private EventHandler<DataSourceChangedEventArgs> m_dataSourceChanged;

    #endregion

    protected abstract IEnumerable<SortDescription> GetPrimaryKeyInfo();

    protected virtual IEnumerable<FilterOperator> GetSupportedFilterOperators( Type dataType )
    {
      if( dataType == null )
        yield break;

      // Nullable<> is an acceptable type if the underlying type is also acceptable.
      if( dataType.IsNullableStruct() )
      {
        dataType = Nullable.GetUnderlyingType( dataType );
      }

      // Here are the data types that supports the equality operators (==, !=).
      if( dataType.IsPrimitive
        || dataType.IsEnum
        || typeof( IEquatable<> ).MakeGenericType( dataType ).IsAssignableFrom( dataType )
        || typeof( IComparable ).IsAssignableFrom( dataType ) )
      {
        yield return FilterOperator.Equal;
        yield return FilterOperator.NotEqual;
      }

      // Here are the data types that supports the basic comparison operators (<, <=, >, >=).
      if( dataType.IsPrimitive
        || dataType.IsEnum
        || typeof( IComparable ).IsAssignableFrom( dataType ) )
      {
        yield return FilterOperator.LessThan;
        yield return FilterOperator.LessThanOrEqual;
        yield return FilterOperator.GreaterThan;
        yield return FilterOperator.GreaterThanOrEqual;
      }

      // Here are the data types that supports the searching functions (Contains, StartsWith, EndsWith, ...).
      if( typeof( string ) == dataType )
      {
        yield return FilterOperator.StartsWith;
        yield return FilterOperator.EndsWith;
        yield return FilterOperator.Contains;
        yield return FilterOperator.ContainedIn;
      }
    }

    protected virtual IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      yield break;
    }

    protected virtual bool CanSortBy( IEnumerable<SortDescription> sortOrder )
    {
      if( sortOrder != null )
      {
        foreach( SortDescription sd in sortOrder )
        {
          Type propertyType;
          if( !this.FindElementMemberType( sd.PropertyName, out propertyType ) )
            return false;

          // Nullable<> is an acceptable type if the underlying type is also acceptable.
          if( propertyType.IsNullableStruct() )
          {
            propertyType = Nullable.GetUnderlyingType( propertyType );
          }

          if( !typeof( IComparable ).IsAssignableFrom( propertyType ) )
            return false;
        }
      }

      return true;
    }

    internal virtual IEnumerable<FilterOperator> SupportedFilterOperators( Type dataType )
    {
      return this.GetSupportedFilterOperators( dataType );
    }

    internal virtual IEnumerable<FilterOperator> SupportedFilterOperators( string dataString )
    {
      Type elementType = this.GetElementMemberType( dataString );
      return this.GetSupportedFilterOperators( elementType );
    }

    internal virtual IEnumerable<Type> SupportedStatFunctions( Type dataType )
    {
      return this.GetSupportedStatFunctions( dataType );
    }

    internal virtual bool IsStatFunctionSupportedNatively( StatFunction statFunction )
    {
      return this.IsStatFunctionSupported( statFunction );
    }

    internal bool IsStatFunctionSupported( StatFunction statFunction )
    {
      if( statFunction == null )
        return false;

      List<Type> dataTypes = new List<Type>();

      if( statFunction.GetSourcePropertyNames().Count > 0 )
      {
        dataTypes.AddRange( ( from spn in statFunction.GetSourcePropertyNames()
                              select this.GetElementMemberType( spn ) ).Distinct() );
      }
      else
      {
        dataTypes.Add( null );
      }

      Type statFunctionType = statFunction.GetType();
      bool isSupported = false;

      foreach( Type dataType in dataTypes )
      {
        isSupported = false;

        foreach( Type sft in this.SupportedStatFunctions( dataType ) )
        {
          isSupported = ( sft == statFunctionType )
                     || ( sft.IsAbstract && sft.IsAssignableFrom( statFunctionType ) );

          if( isSupported )
            break;
        }

        if( !isSupported )
          break;
      }

      return isSupported;
    }

    internal virtual bool IsSortOrderSupported( IEnumerable<SortDescription> sortOrder )
    {
      return this.CanSortBy( sortOrder );
    }

    internal bool CanFilterBy( Predicate<object> predicate )
    {
      return ( predicate == null )
          || ( this.CanFilterPredicate );
    }

    internal bool CanFilterBy( FilterExpressionBase filterExpression )
    {
      // There is no filter at all.
      if( filterExpression == null )
        return true;

      // The DataSourceProvider doesn't support filtering at all.
      if( !this.CanFilter )
        return false;

      CanFilterByVisitor verifier = new CanFilterByVisitor(
                                      filterExpression,
                                      this.GetElementMemberType,
                                      this.GetSupportedFilterOperators );

      return verifier.Result;
    }

    internal virtual bool CanJumpTo( IEnumerable<SortDescription> properties )
    {
      return this.CanJump;
    }

    internal virtual int GetPageSize()
    {
      return this.PageSize;
    }

    internal void CheckMinimumRequirements()
    {
      int keyCount = this.PrimaryKeyInfo.Count();

      // A primary key must exists.
      if( keyCount <= 0 )
        throw new InvalidOperationException( "An attempt was made to use a data source that has not provided a primary key." );
    }

    internal abstract IAsyncResult BeginExecuteCountQueryCore(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract CountQueryResult EndExecuteCountQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteDataQueryCore(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract DataQueryResult EndExecuteDataQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteGroupCountQueryCore(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract GroupCountQueryResult EndExecuteGroupCountQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteStatQueryCore(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract StatQueryResult EndExecuteStatQueryCore( IAsyncResult asyncResult );

    internal IAsyncResult BeginExecuteCountQueryInternal(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanCount )
        throw new DataInternalException( "The underlying data source does not support counting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteCountQueryCallId,
               ( cb, st ) => this.BeginExecuteCountQueryCore( queryParameters, cb, st ),
               this.EndExecuteCountQueryCore,
               callback,
               state );
    }

    internal CountQueryResult EndExecuteCountQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteCountQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      CountQueryResult queryResult = result.Item1 as CountQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.Count < 0 )
        throw new DataSourceProviderException( "The count value cannot be less than zero." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteDataQueryInternal(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( ( queryParameters.Skip != null ) && ( !this.CanSkip ) )
        throw new DataInternalException( "The underlying data source does not support skipping." );

      if( ( queryParameters.Sort != null ) && ( !this.CanSort ) )
        throw new DataInternalException( "The underlying data source does not support sorting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.ItemJump != null ) && ( !this.CanJumpToItem ) )
        throw new DataInternalException( "The underlying data source does not support jumping to items." );

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteDataQueryCallId,
               ( cb, st ) => this.BeginExecuteDataQueryCore( queryParameters, cb, st ),
               this.EndExecuteDataQueryCore,
               callback,
               state );
    }

    internal DataQueryResult EndExecuteDataQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteDataQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      DataQueryResult queryResult = result.Item1 as DataQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.InlineCount.GetValueOrDefault( 0 ) < 0 )
        throw new DataSourceProviderException( "The inline count cannot be less than zero." );

      if( queryResult.TotalCount.GetValueOrDefault( 0 ) < 0 )
        throw new DataSourceProviderException( "The total count cannot be less than zero." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteGroupCountQueryInternal(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanGroupCount )
        throw new DataInternalException( "The underlying data source does not support group counting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteGroupCountQueryCallId,
               ( cb, st ) => this.BeginExecuteGroupCountQueryCore( queryParameters, cb, st ),
               this.EndExecuteGroupCountQueryCore,
               callback,
               state );
    }

    internal GroupCountQueryResult EndExecuteGroupCountQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteGroupCountQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      GroupCountQueryResult queryResult = result.Item1 as GroupCountQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.GroupCounts == null )
        throw new DataSourceProviderException( "The query result must provided a GroupCounts value." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteStatQueryInternal(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanCalculateStats )
        throw new DataInternalException( "The underlying data source does not support statistical functions." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      StatFunction statFunction = ( queryParameters.Function != null )
                                    ? queryParameters.Function.StatFunction
                                    : null;

      if( statFunction == null )
        throw new DataInternalException();

      this.CheckIfStatFunctionIsInCalculation( statFunction );
      this.InitializeStatFunction( statFunction );

      IAsyncResult asyncResult;

      // The StatFunction is supported as it is by the DataSourceProvider.
      if( this.IsStatFunctionSupportedNatively( statFunction ) )
      {
        asyncResult = this.StartNativeStatQuery(
                        queryParameters,
                        statFunction,
                        null,
                        callback,
                        state );
      }
      // The StatFunction isn't supported as it is by the DataSourceProvider.
      // Try to calculate the StatFunction with its prerequisite StatFunctions.
      else
      {
        asyncResult = this.StartPrerequisiteStatQuery(
                        queryParameters,
                        statFunction,
                        callback,
                        state );
      }

      return asyncResult;
    }

    internal StatQueryResult EndExecuteStatQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteStatQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      StatQueryResult queryResult = result.Item1 as StatQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
      {
        queryResult = new StatQueryResult( new StatResult( exception ) );
      }

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal void CancelQuery( IAsyncResult asyncResult )
    {
      m_taskScheduler.Cancel( asyncResult );
    }

    private IAsyncResult BeginExecute(
      Guid callId,
      Func<AsyncCallback, object, IAsyncResult> beginExecuteMethod,
      Func<IAsyncResult, object> endExecuteMethod,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult( callId, callback, state );

      bool isCompletedSynchronously = true;
      IAsyncResult taskId = asyncResult;
      RetryAttempt taskRetryAttempt = new RetryAttempt();

      AsyncCallback queryCallback = ( IAsyncResult ar ) =>
      {
        Action<object> handleQueryResult = null;

        // The result must not be returned synchronously if the task
        // has been delayed by the task scheduler.
        if( isCompletedSynchronously && ar.CompletedSynchronously )
        {
          handleQueryResult = asyncResult.StartSync;
        }
        else
        {
          handleQueryResult = ( object qr ) =>
          {
            try
            {
              this.Dispatcher.BeginInvoke( new Action( () =>
              {
                asyncResult.Start( qr, true );
              } ) );
            }
            catch( Exception e )
            {
              Exception callbackException = e;

              if( !( e is DataSourceProviderException ) )
              {
                callbackException = new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", e );
              }

              this.HandleAsyncException( callbackException );
            }
          };
        }

        this.EndExecute(
          handleQueryResult,
          endExecuteMethod,
          ar,
          taskId,
          taskRetryAttempt );
      };

      Action task = () =>
      {
        IAsyncResult ar = null;

        try
        {
          ar = beginExecuteMethod.Invoke( queryCallback, null );
        }
        catch( Exception e )
        {
          ErrorResultAction errorResult = this.HandleException( e, taskRetryAttempt.Count );

          if( errorResult == ErrorResultAction.Retry )
          {
            taskRetryAttempt.Increment();
            m_taskScheduler.Restart( taskId );
          }
          else if( errorResult == ErrorResultAction.Abort )
          {
            AsyncResult exceptionAsyncResult = new AsyncResult( DataSourceProvider.ExecuteExceptionCallId, queryCallback, null );

            if( isCompletedSynchronously )
            {
              exceptionAsyncResult.StartSync( e );
            }
            else
            {
              exceptionAsyncResult.StartAsync( e );
            }
          }
          else
          {
            // A new type of result has been created but hasn't been handled.
            if( e is DataSourceProviderException )
            {
              throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", e );
            }
            else
            {
              throw;
            }
          }
        }
      };

      // Add the current task to the task scheduler's queue.  
      // The task may not be called right away.
      m_taskScheduler.Enqueue( taskId, task );

      // Any task completed after this point is considered asynchronous.
      isCompletedSynchronously = false;

      return asyncResult;
    }

    private void EndExecute(
      Action<object> sendResultMethod,
      Func<IAsyncResult, object> endExecuteMethod,
      IAsyncResult asyncResult,
      IAsyncResult taskId,
      RetryAttempt taskRetryAttempt )
    {
      object queryResult = null;
      Exception exception = null;

      if( asyncResult.UseCallId( DataSourceProvider.ExecuteExceptionCallId ) )
      {
        exception = asyncResult.GetCallResult<Exception>();
      }
      else
      {
        try
        {
          queryResult = endExecuteMethod.Invoke( asyncResult );
        }
        catch( Exception e )
        {
          exception = e;
        }
      }

      // Any operation on the query result or any error handling must be
      // done on the thread that called BeginExecute in the first place
      // to avoid any racing condition.
      Action handleResult = () =>
      {
        bool retryTask = false;

        // Check for any error on the call to EndExecute.
        if( exception != null )
        {
          ErrorResultAction errorResult = this.HandleException( exception, taskRetryAttempt.Count );

          if( errorResult == ErrorResultAction.Retry )
          {
            taskRetryAttempt.Increment();
            retryTask = true;
          }
          else if( errorResult == ErrorResultAction.Abort )
          {
            // The exception will be thrown on the call to EndExecute*Query.
          }
          else
          {
            // A new type of result has been created but hasn't been handled.
            if( exception is DataSourceProviderException )
            {
              throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );
            }
            else
            {
              throw new DataInternalException( exception );
            }
          }
        }

        bool isTaskCanceled = m_taskScheduler.IsCanceled( taskId );

        if( retryTask && !isTaskCanceled )
        {
          m_taskScheduler.Restart( taskId );
        }
        else
        {
          m_taskScheduler.Dequeue( taskId );

          if( !isTaskCanceled )
          {
            Tuple<object, Exception> result = new Tuple<object, Exception>(
                                                    queryResult,
                                                    exception );

            sendResultMethod.Invoke( result );
          }
        }
      };

      handleResult.Invoke();
    }

    #region Initialization Methods

    protected virtual void Initialize()
    {
    }

    internal void Init( Dispatcher dispatcher )
    {
      if( dispatcher == null )
        throw new ArgumentNullException( "dispatcher" );

      // A DataSourceProvider can only use one dispatcher.
      if( ( m_dispatcher != null ) && ( m_dispatcher != dispatcher ) )
        throw new DataInternalException();

      if( m_isInitialized )
        return;

      m_dispatcher = dispatcher;
      m_synchonizationContext = new DispatcherSynchronizationContext( dispatcher );
      m_taskScheduler = new TaskScheduler( ( this as IAsyncDataSourceProvider ) );

      this.Initialize();

      m_isInitialized = true;
    }

    private bool m_isInitialized; //false

    #endregion

    #region GetElementMemberType Methods

    internal virtual bool FindElementMemberType( string memberName, out Type type )
    {
      type = null;

      Type elementType = this.ElementType;

      if( memberName == "." )
      {
        type = elementType;
        return true;
      }

      type = ComplexStringHelper.GetExpressionTypeFromString( memberName, elementType );

      return (type != null);    
    }

    internal Type GetElementMemberType( string memberName )
    {
      if( memberName == null )
        throw new ArgumentNullException( "memberName" );

      Type type;

      if( this.FindElementMemberType( memberName, out type ) )
        return type;

      throw new ArgumentException( "The specified member name does not exist.", "memberName" );
    }

    internal bool TryGetElementMemberType( string memberName, out Type type )
    {
      type = null;

      return this.FindElementMemberType( memberName, out type );
    }

    #endregion

    #region Items Wrapping/Unwrapping Methods

    internal RawDataItem WrapDataObject( object item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      RawDataItem dataItem;
      if( !this.TryWrapDataObject( item, out dataItem ) )
        throw new DataInternalException();

      return dataItem;
    }

    internal virtual bool TryWrapDataObject( object item, out RawDataItem dataItem )
    {
      if( item != null )
      {
        // Retrieve the underlying user object.
        dataItem = item as RawDataItem;
        object userObject = ( dataItem != null )
                              ? dataItem.Data
                              : item;

        // Make sure the user object is of the appropriate type.
        if( this.IsOfElementType( userObject ) )
        {
          // Wrap the user object if it wasn't already wrapped.
          if( dataItem == null )
          {
            dataItem = new RawDataItem( item );
          }

          return true;
        }
      }

      dataItem = null;
      return false;
    }

    internal object UnwrapDataObject( RawDataItem dataItem )
    {
      if( dataItem == null )
        throw new ArgumentNullException( "dataItem" );

      object item;
      if( !this.TryUnwrapDataObject( dataItem, out item ) )
        throw new DataInternalException();

      return item;
    }

    internal virtual bool TryUnwrapDataObject( RawDataItem dataItem, out object item )
    {
      if( dataItem != null )
      {
        object userObject = dataItem.Data;

        if( this.IsOfElementType( userObject ) )
        {
          item = userObject;
          return true;
        }
      }

      item = null;
      return false;
    }

    internal object UnwrapLayer( RawDataItem dataItem )
    {
      if( dataItem == null )
        throw new ArgumentNullException( "dataItem" );

      object item;
      if( !this.TryUnwrapLayer( dataItem, out item ) )
        throw new DataInternalException( "The item cannot be retrieved." );

      return item;
    }

    internal virtual bool TryUnwrapLayer( RawDataItem dataItem, out object item )
    {
      return this.TryUnwrapDataObject( dataItem, out item );
    }

    private IEnumerable<RawDataItem> WrapDataObjects( IEnumerable items )
    {
      if( items == null )
        yield break;

      foreach( object item in items )
      {
        yield return this.WrapDataObject( item );
      }
    }

    private CountQueryResult WrapDataObjectQueryResult( CountQueryResult queryResult )
    {
      return queryResult;
    }

    private DataQueryResult WrapDataObjectQueryResult( DataQueryResult queryResult )
    {
      if( queryResult == null )
        return queryResult;

      IEnumerable<RawDataItem> dataItems = this.WrapDataObjects( queryResult.Data );

      return new DataQueryResult(
               dataItems,
               queryResult.InlineCount,
               queryResult.TotalCount );
    }

    private GroupCountQueryResult WrapDataObjectQueryResult( GroupCountQueryResult queryResult )
    {
      return queryResult;
    }

    private StatQueryResult WrapDataObjectQueryResult( StatQueryResult queryResult )
    {
      return queryResult;
    }

    private IEnumerable<ItemChangedDescription> WrapDataObjectItemsChangedDescription( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        yield break;

      foreach( ItemChangedDescription itemChanged in itemsChanged )
      {
        object item = itemChanged.Item;
        RawDataItem dataItem;

        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          ItemChangedDescription newItemChanged = ( !object.Equals( item, dataItem ) )
                                                    ? itemChanged.CreateWithNewItem( dataItem )
                                                    : itemChanged;

          yield return newItemChanged;
        }
      }
    }

    private DataSourceChangedEventArgs WrapDataObjectEventArgs(
      DataSourceChangedEventArgs eventArgs )
    {
      DataSourceChangedAction action = eventArgs.Action;
      IEnumerable<RawDataItem> newItems = this.WrapDataObjects( eventArgs.NewItems );
      IEnumerable<RawDataItem> oldItems = this.WrapDataObjects( eventArgs.OldItems );
      IEnumerable<ItemChangedDescription> itemsChanged = this.WrapDataObjectItemsChangedDescription( eventArgs.ItemsChanged );

      DataSourceChangedEventArgs newEventArgs;
      if( action == DataSourceChangedAction.ItemChanged )
      {
        newEventArgs = new DataSourceChangedEventArgs( itemsChanged );
      }
      else
      {
        newEventArgs = new DataSourceChangedEventArgs( action, newItems.Cast<object>(), oldItems.Cast<object>() );
      }

      return newEventArgs;
    }

    private IEnumerable<object> UnwrapLayer( IEnumerable items )
    {
      if( items != null )
      {
        foreach( object item in items )
        {
          yield return this.UnwrapLayer( ( RawDataItem )item );
        }
      }
    }

    private CountQueryParameters UnwrapLayer(
      CountQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private DataQueryParameters UnwrapLayer(
      DataQueryParameters queryParameters )
    {
      if( queryParameters.ItemJump == null )
        return queryParameters;

      RawDataItem itemJump = ( RawDataItem )queryParameters.ItemJump.Item;
      object newItemJump = this.UnwrapLayer( itemJump );
      if( object.Equals( newItemJump, itemJump ) )
        return queryParameters;

      ItemJumpRequest newItemJumpRequest = new ItemJumpRequest( newItemJump );
      return new DataQueryParameters(
               queryParameters.Take,
               queryParameters.Skip,
               queryParameters.Sort,
               queryParameters.Filter,
               newItemJumpRequest,
               queryParameters.Jump );
    }

    private GroupCountQueryParameters UnwrapLayer(
      GroupCountQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private StatQueryParameters UnwrapLayer(
      StatQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private IEnumerable<ItemChangedDescription> UnwrapLayer( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        yield break;

      foreach( ItemChangedDescription itemChanged in itemsChanged )
      {
        RawDataItem dataItem = ( RawDataItem )itemChanged.Item;
        object item = this.UnwrapLayer( dataItem );

        yield return ( !object.Equals( item, dataItem ) )
                       ? itemChanged.CreateWithNewItem( dataItem )
                       : itemChanged;
      }
    }

    private bool IsOfElementType( object item )
    {
      return ( item != null ) && ( this.ElementType.IsAssignableFrom( item.GetType() ) );
    }

    #endregion

    #region StatFunctions Engine Methods

    internal virtual void CheckIfStatFunctionIsInCalculation( StatFunction statFunction )
    {
      if( ( statFunction != null ) && ( statFunction.InCalculation ) )
        throw new DataInternalException();
    }

    internal virtual void SetStatFunctionCalculationState(
      StatFunction statFunction,
      bool inCalculation )
    {
      if( statFunction == null )
        return;

      if( inCalculation )
      {
        statFunction.StartCalculation();
      }
      else
      {
        statFunction.EndCalculation();
      }
    }

    private Func<AsyncCallback, object, IAsyncResult> CreateBeginExecuteStatQuery(
      Func<StatQueryParameters, AsyncCallback, object, IAsyncResult> method,
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      Debug.Assert( ( method != null )
                 && ( statFunction != null ) );

      return
        ( cb, st ) =>
        {
          try
          {
            DataSourceProvider.PreparePrerequisiteFunctions( statFunction, statResults );

            this.SetStatFunctionCalculationState( statFunction, true );

            return method.Invoke( queryParameters, cb, st );
          }
          catch( Exception )
          {
            this.SetStatFunctionCalculationState( statFunction, false );
            throw;
          }
        };
    }

    private Func<IAsyncResult, StatQueryResult> CreateEndExecuteStatQuery(
      Func<IAsyncResult, StatQueryResult> method,
      StatFunction statFunction )
    {
      Debug.Assert( ( method != null )
                 && ( statFunction != null ) );

      return
        ( iar ) =>
        {
          try
          {
            return method.Invoke( iar );
          }
          catch( Exception e )
          {
            return new StatQueryResult( new StatResult( e ) );
          }
          finally
          {
            this.SetStatFunctionCalculationState( statFunction, false );
          }
        };
    }

    private IAsyncResult StartNativeStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( statFunction != null );

      // This anonymous method start the calculation of the StatFunction.
      Func<AsyncCallback, object, IAsyncResult> beginExecute =
        this.CreateBeginExecuteStatQuery(
          this.BeginExecuteStatQueryCore,
          queryParameters,
          statFunction,
          statResults );

      // This anonymous method retrieve the result of the StatFunction.
      Func<IAsyncResult, StatQueryResult> getResult =
        this.CreateEndExecuteStatQuery(
          this.EndExecuteStatQueryCore,
          statFunction );
      Func<IAsyncResult, object> endExecute = ( iar ) => getResult.Invoke( iar );

      return this.BeginExecute(
               DataSourceProvider.ExecuteStatQueryCallId,
               beginExecute,
               endExecute,
               callback,
               state );
    }

    private IAsyncResult StartPrerequisiteStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( statFunction != null );

      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteStatQueryCallId,
                                  callback,
                                  state );

      // This anonymous method start the calculation of the StatFunction
      // from the values of the prerequisite functions.
      Func<AsyncCallback, object, IAsyncResult> beginExecute =
        this.CreateBeginExecuteStatQuery(
          this.BeginExecuteStatQueryPrerequisiteFunctions,
          queryParameters,
          statFunction,
          null );

      // This anonymous method retrieve the result of the StatFunction.
      Func<IAsyncResult, StatQueryResult> getResult =
        this.CreateEndExecuteStatQuery(
          this.EndExecuteStatQueryPrerequisiteFunctions,
          statFunction );
      Func<AsyncResult, IAsyncResult, DependentCallResult> endExecute =
        ( par, iar ) =>
        {
          StatQueryResult queryResult;
          Exception exception;

          try
          {
            queryResult = getResult.Invoke( iar );
            exception = null;
          }
          catch( Exception e )
          {
            queryResult = null;
            exception = e;
          }

          return new DependentCallResult(
                   new Tuple<object, Exception>( queryResult, exception ) );
        };

      asyncResult.Start( beginExecute, endExecute );

      return asyncResult;
    }

    private IAsyncResult BeginExecuteStatQueryPrerequisiteFunctions(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteStatQueryPrerequisiteFunctionsCallId,
                                  callback,
                                  state );

      StatFunction statFunction = queryParameters.Function.StatFunction;
      List<StatFunction> dependentStatFunctions = new List<StatFunction>();

      // The value cannot be retrieved if one of the StatFunction cannot be evaluated.
      if( !this.GetQueryableStatFunctions( statFunction, dependentStatFunctions ) )
        throw new DataInternalException( "The StatFunction isn't supported by the DataSourceProvider." );

      Debug.Assert( dependentStatFunctions.Count > 0 );

      Dictionary<StatFunction, StatResult> statResults = new Dictionary<StatFunction, StatResult>(
                                                           new StatFunctionEqualityComparer() );
      Queue<Tuple<StatFunction, StatQueryParameters>> remainingQueries = new Queue<Tuple<StatFunction, StatQueryParameters>>();

      // Build the query parameters of each dependent StatFunction.
      foreach( StatFunction function in dependentStatFunctions )
      {
        StatQueryParameters newQueryParameters =
          new StatQueryParameters(
            new StatRequest( function ),
            queryParameters.Sort,
            queryParameters.Filter,
            queryParameters.FilterGroup );

        remainingQueries.Enqueue(
          new Tuple<StatFunction, StatQueryParameters>(
            function,
            newQueryParameters ) );
      }

      // This anonymous method starts a query to retrieve the value of one StatFunction.
      Func<AsyncCallback, object, IAsyncResult> beginGetStatValue =
        ( cb, st ) =>
        {
          Tuple<StatFunction, StatQueryParameters> query = remainingQueries.Peek();
          StatFunction currentStatFunction = query.Item1;

          if( currentStatFunction != statFunction )
            this.CheckIfStatFunctionIsInCalculation( currentStatFunction );

          this.InitializeStatFunction( currentStatFunction );

          return this.BeginExecuteNativeStatQuery(
                   query.Item2,
                   currentStatFunction,
                   statResults,
                   cb,
                   st );
        };

      // This anonymous method gets the value of a stat function and starts a query
      // for the next stat function that hasn't been evaluated yet.
      Func<AsyncResult, IAsyncResult, DependentCallResult> endGetStatValue = null;
      endGetStatValue =
        ( par, iar ) =>
        {
          StatQueryResult queryResult = this.EndExecuteNativeStatQuery( iar );
          StatResult statResult = queryResult.Result;

          if( statResult == null )
            return new DependentCallResult( queryResult );

          // Since the result was retrieve asynchronously, the previous
          // query results may not be up to date anymore.  We'll flag them
          // has estimated.
          if( !iar.CompletedSynchronously )
          {
            foreach( StatResult sr in statResults.Values )
            {
              sr.IsEstimate = true;
            }
          }

          StatFunction currentStatFunction = remainingQueries.Dequeue().Item1;
          statResults.Add( currentStatFunction, statResult );

          if( currentStatFunction == statFunction )
            return new DependentCallResult( queryResult );

          Debug.Assert( remainingQueries.Count > 0 );

          return new DependentCallResult( null, beginGetStatValue, endGetStatValue );
        };

      asyncResult.Start( beginGetStatValue, endGetStatValue );

      return asyncResult;
    }

    private StatQueryResult EndExecuteStatQueryPrerequisiteFunctions( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteStatQueryPrerequisiteFunctionsCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private IAsyncResult BeginExecuteNativeStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( ( statFunction != null ) && ( statResults != null ) );

      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteNativeStatQueryCallId,
                                  callback,
                                  state );

      StatResult statResult;

      // The StatFunction hasn't been calculated yet.
      if( !statResults.TryGetValue( statFunction, out statResult ) )
      {
        StatFunction[] prerequisiteFunctions = statFunction.PrerequisiteFunctions;
        bool canCalculateResult = ( !statFunction.RequiresAccumulation )
                               && ( prerequisiteFunctions != null )
                               && ( prerequisiteFunctions.Length > 0 )
                               && ( ( from pf in prerequisiteFunctions
                                      select statResults.ContainsKey( pf ) ).All( ( result ) => result ) );

        // The result can be calculated right away.
        if( canCalculateResult )
        {
          try
          {
            DataSourceProvider.PreparePrerequisiteFunctions( statFunction, statResults );

            statResult = statFunction.GetResult();
          }
          catch( Exception e )
          {
            statResult = new StatResult( e );
          }

          asyncResult.StartSync( new StatQueryResult( statResult ) );
        }
        else
        {
          asyncResult.Start(
            ( cb, st ) => this.StartNativeStatQuery( queryParameters, statFunction, statResults, cb, st ),
            ( par, iar ) => new DependentCallResult( this.EndExecuteStatQueryInternal( iar ) ) );
        }
      }
      // The StatFunction has already been calculated earlier.
      else
      {
        asyncResult.StartSync( new StatQueryResult( statResult ) );
      }

      return asyncResult;
    }

    private StatQueryResult EndExecuteNativeStatQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteNativeStatQueryCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private void InitializeStatFunction( StatFunction statFunction )
    {
      if( statFunction == null )
        return;

      if( !statFunction.IsInitialized )
      {
        // Make sure the target StatFunction is valid.
        statFunction.Validate();

        IEnumerable<Type> dataTypes = ( from spn in statFunction.GetSourcePropertyNames()
                                        let mt = this.GetElementMemberType( spn )
                                        select ( mt.IsNullableStruct() )
                                                 ? Nullable.GetUnderlyingType( mt )
                                                 : mt );

        // Initialize the accumulators type.
        statFunction.InitializeAccumulationTypes( dataTypes.ToArray() );
      }
    }

    private static void PreparePrerequisiteFunctions(
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      statFunction.Reset();

      if( statResults != null )
      {
        // Initialize the prerequisite if necessary.
        StatResult[] prerequisiteValues = DataSourceProvider.GetPrerequisiteValues(
                                            statFunction,
                                            statResults );

        if( ( prerequisiteValues != null ) && ( prerequisiteValues.Length > 0 ) )
        {
          for( int i = 0; i < prerequisiteValues.Length; i++ )
          {
            if( prerequisiteValues[ i ].Value is Exception )
              throw ( Exception )prerequisiteValues[ i ].Value;
          }

          statFunction.InitializePrerequisites( prerequisiteValues );
        }
      }
    }

    private bool GetQueryableStatFunctions(
      StatFunction targetStatFunction,
      IList<StatFunction> queryableStatFunctions )
    {
      if( targetStatFunction == null )
        return false;

      IEqualityComparer<StatFunction> comparer = new StatFunctionEqualityComparer();

      // The target StatFunction isn't planned already.
      if( !queryableStatFunctions.Contains( targetStatFunction, comparer ) )
      {
        // The target StatFunction is supported as it is.
        if( this.IsStatFunctionSupportedNatively( targetStatFunction ) )
        {
          queryableStatFunctions.Add( targetStatFunction );
        }
        // Check out if the StatFunction may be calculated from its dependent StatFunction.
        else
        {
          StatFunction[] prerequisiteFunctions = targetStatFunction.PrerequisiteFunctions;

          // The target StatFunction must use prerequisite functions.
          if( ( prerequisiteFunctions == null )
            || ( prerequisiteFunctions.Length <= 0 )
            || ( ( targetStatFunction.RequiresAccumulation )
              && ( !this.IsStatFunctionSupported( targetStatFunction ) ) ) )
            return false;

          foreach( StatFunction dependentStatFunction in prerequisiteFunctions )
          {
            if( !this.GetQueryableStatFunctions( dependentStatFunction, queryableStatFunctions ) )
              return false;
          }

          queryableStatFunctions.Add( targetStatFunction );
        }
      }

      return true;
    }

    private static StatResult[] GetPrerequisiteValues(
      StatFunction targetStatFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      Debug.Assert( targetStatFunction != null );

      StatFunction[] prerequisiteFunctions = targetStatFunction.PrerequisiteFunctions;

      if( ( prerequisiteFunctions != null ) && ( prerequisiteFunctions.Length > 0 ) )
      {
        Debug.Assert( statResults != null );

        StatResult[] prerequisiteValues = new StatResult[ prerequisiteFunctions.Length ];

        for( int i = 0; i < prerequisiteFunctions.Length; i++ )
        {
          StatFunction prerequisiteFunction = prerequisiteFunctions[ i ];
          StatResult prerequisiteValue;

          if( !statResults.TryGetValue( prerequisiteFunction, out prerequisiteValue ) )
            throw new DataInternalException();

          prerequisiteValues[ i ] = prerequisiteValue;
        }

        return prerequisiteValues;
      }

      return null;
    }

    #endregion

    #region External Notification Methods

    public void NotifyReset()
    {
      this.InvalidateData();
    }

    public void NotifyItemsAdded( object itemAdded )
    {
      if( itemAdded == null )
        throw new ArgumentNullException( "itemAdded" );

      this.NotifyItemsAdded( new object[] { itemAdded } );
    }

    public void NotifyItemsAdded( IEnumerable itemsAdded )
    {
      if( itemsAdded == null )
        return;

      IEnumerable<object> items = itemsAdded.Cast<object>();
      if( !items.Any() )
        return;

      this.OnDataObjectsAdded( items );
    }

    public void NotifyItemsRemoved( object itemRemoved )
    {
      if( itemRemoved == null )
        throw new ArgumentNullException( "itemRemoved" );

      this.NotifyItemsRemoved( new object[] { itemRemoved } );
    }

    public void NotifyItemsRemoved( IEnumerable itemsRemoved )
    {
      if( itemsRemoved == null )
        return;

      // Retrieve the items that are about to be removed.
      List<RawDataItem> dataItemsRemoved = new List<RawDataItem>();
      foreach( object item in itemsRemoved )
      {
        RawDataItem dataItem;
        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          dataItemsRemoved.Add( dataItem );
        }
      }

      IEnumerable<object> unwrappedItemsRemoved = this.UnwrapLayer( dataItemsRemoved );
      if( unwrappedItemsRemoved.Any() )
      {
        this.OnDataObjectsRemoved( unwrappedItemsRemoved );
      }
    }

    public void NotifyItemsChanged( ItemChangedDescription itemChanged )
    {
      if( itemChanged != null )
      {
        this.NotifyItemsChanged( new ItemChangedDescription[] { itemChanged } );
      }
    }

    public void NotifyItemsChanged( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( ( itemsChanged == null ) || ( !itemsChanged.Any() ) )
        return;

      IEnumerable<ItemChangedDescription> wrappedItemsChanged = this.WrapDataObjectItemsChangedDescription( itemsChanged );

      this.InvalidateDataItem( wrappedItemsChanged );
    }

    #endregion

    #region Internal Notification Methods

    protected virtual void OnInvalidateData()
    {
      this.OnDataSourceChanged( new DataSourceChangedEventArgs( DataSourceChangedAction.Reset ) );
    }

    protected virtual void OnInvalidateDataObjects( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      this.OnDataSourceChanged( new DataSourceChangedEventArgs( itemsChanged ) );
    }

    protected virtual void OnDataObjectsAdded( IEnumerable<object> itemsAdded )
    {
      List<object> dataItemsAdded = new List<object>();
      foreach( object item in itemsAdded )
      {
        RawDataItem dataItem;
        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          dataItemsAdded.Add( dataItem );
        }
      }

      if( dataItemsAdded.Count > 0 )
      {
        DataSourceChangedEventArgs eventArgs = new DataSourceChangedEventArgs(
                                                 DataSourceChangedAction.Add,
                                                 dataItemsAdded,
                                                 null );
        this.OnDataSourceChanged( eventArgs );
      }
    }

    protected virtual void OnDataObjectsRemoved( IEnumerable<object> itemsRemoved )
    {
      if( itemsRemoved.Any() )
      {
        DataSourceChangedEventArgs eventArgs = new DataSourceChangedEventArgs(
                                                 DataSourceChangedAction.Remove,
                                                 null,
                                                 itemsRemoved );
        this.OnDataSourceChanged( eventArgs );
      }
    }

    internal virtual void InvalidateData()
    {
      this.OnInvalidateData();
    }

    internal void InvalidateDataItem( RawDataItem item )
    {
      ItemChangedDescription itemChanged = new ItemChangedDescription( item );
      this.InvalidateDataItem( itemChanged );
    }

    internal void InvalidateDataItem( ItemChangedDescription itemChanged )
    {
      if( itemChanged == null )
        throw new ArgumentNullException( "itemChanged" );

      this.InvalidateDataItem( new ItemChangedDescription[] { itemChanged } );
    }

    internal virtual void InvalidateDataItem( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        throw new ArgumentNullException( "itemsChanged" );

      // There is no need to go further if no item has changed.
      List<ItemChangedDescription> itemsChangedList = itemsChanged.ToList();
      if( itemsChangedList.Count <= 0 )
        return;

      Type elementType = this.ElementType;
      foreach( ItemChangedDescription itemChanged in itemsChangedList )
      {
        RawDataItem dataItem = itemChanged.Item as RawDataItem;
        if( dataItem == null )
          throw new DataInternalException();

        // Make sure the data object is of the user object type.
        if( !this.IsOfElementType( dataItem.Data ) )
          throw new DataInternalException();
      }

      IEnumerable<ItemChangedDescription> unwrappedItemsChanged = this.UnwrapLayer( itemsChangedList );

      this.OnInvalidateDataObjects( unwrappedItemsChanged );
    }

    #endregion

    #region Private Fields

    private TaskScheduler m_taskScheduler; //null
    private DispatcherSynchronizationContext m_synchonizationContext; //null

    #endregion

    #region Private RetryAttempt Nested Type

    private class RetryAttempt
    {
      internal RetryAttempt()
      {
        this.Count = 0;
      }

      internal int Count
      {
        get;
        private set;
      }

      internal void Increment()
      {
        if( this.Count == Int32.MaxValue )
          return;

        this.Count++;
      }
    }

    #endregion

    #region Private ScheduleTask Nested Type

    private class ScheduleTask
    {
      internal ScheduleTask( IAsyncResult taskId, Action task )
      {
        if( taskId == null )
          throw new ArgumentNullException( "taskId" );

        if( task == null )
          throw new ArgumentNullException( "task" );

        this.Id = taskId;
        this.Task = task;
        this.IsCanceled = false;
      }

      internal IAsyncResult Id
      {
        get;
        private set;
      }

      internal Action Task
      {
        get;
        private set;
      }

      internal bool IsCanceled
      {
        get;
        private set;
      }


      internal void Cancel()
      {
        this.IsCanceled = true;
      }
    }

    #endregion

    #region Private TaskScheduler Nested Type

    private class TaskScheduler
    {
      internal TaskScheduler( IAsyncDataSourceProvider throttler )
      {
        m_throttler = throttler;
      }

      private ScheduleTask CurrentTask
      {
        get
        {
          return m_queue.First.Value;
        }
      }

      private static bool IsSameTask( ScheduleTask scheduledTask, IAsyncResult taskId )
      {
        return ( scheduledTask != null && object.ReferenceEquals( scheduledTask.Id, taskId ) );
      }

      private bool IsCurrentTask( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return false;

        return TaskScheduler.IsSameTask( this.CurrentTask, taskId );
      }

      private ScheduleTask Find( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return null;

        foreach( ScheduleTask scheduledTask in m_queue )
        {
          if( TaskScheduler.IsSameTask( scheduledTask, taskId ) )
            return scheduledTask;
        }

        return null;
      }

      private bool Contains( IAsyncResult taskId )
      {
        return ( this.Find( taskId ) != null );
      }

      private bool RemoveTask( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return false;

        bool removed = false;
        LinkedListNode<ScheduleTask> node = m_queue.First;

        while( node != null )
        {
          if( TaskScheduler.IsSameTask( node.Value, taskId ) )
          {
            LinkedListNode<ScheduleTask> nextNode = node.Next;

            m_queue.Remove( node );
            removed = true;

            node = nextNode;
          }
          else
          {
            node = node.Next;
          }
        }

        return removed;
      }

      private void InvokeTask()
      {
        Action startTask = () =>
        {
          Action task = null;

          lock( m_lock )
          {
            while( m_queue.Count > 0 )
            {
              ScheduleTask currentTask = this.CurrentTask;

              if( currentTask.IsCanceled )
              {
                m_queue.RemoveFirst();
              }
              else
              {
                task = currentTask.Task;
                break;
              }
            }
          }

          if( task != null )
          {
            m_lastTickCount = Environment.TickCount;
            task.Invoke();
          }
        };

        int waitTickCount = 0;

        if( m_throttler != null && m_lastTickCount.HasValue )
        {
          int waitTime = Convert.ToInt32( m_throttler.QueryRequestInterval.TotalMilliseconds );
          int elapseTime = Environment.TickCount - m_lastTickCount.Value;

          waitTickCount = waitTime - elapseTime;
        }

        if( waitTickCount <= 0 )
        {
          startTask.Invoke();
        }
        else
        {
          DispatcherTimer timer = new DispatcherTimer();

          timer.Interval = new TimeSpan( 0, 0, 0, 0, waitTickCount );
          timer.Tick += ( object sender, EventArgs e ) =>
          {
            DispatcherTimer oneTime = ( DispatcherTimer )sender;
            oneTime.Stop();

            startTask.Invoke();
          };

          timer.Start();
        }
      }

      internal bool Enqueue( IAsyncResult taskId, Action task )
      {
        if( taskId == null )
          throw new ArgumentNullException( "taskId" );

        if( task == null )
          throw new ArgumentNullException( "task" );

        bool invokeNow = false;

        lock( m_lock )
        {
          if( this.Contains( taskId ) )
            return false;

          ScheduleTask scheduledTask = new ScheduleTask( taskId, task );

          m_queue.AddLast( scheduledTask );

          // Start the task right away if it is the only task in the queue.
          invokeNow = ( m_queue.Count == 1 );
        }

        if( invokeNow )
        {
          this.InvokeTask();
        }

        return true;
      }

      internal bool Restart( IAsyncResult taskId )
      {
        lock( m_lock )
        {
          if( taskId == null || m_queue.Count <= 0 )
            return false;

          if( !this.IsCurrentTask( taskId ) )
            throw new InvalidOperationException( "An attempt was made to restart a task that has not been started yet." );

          ScheduleTask currentTask = this.CurrentTask;

          this.RemoveTask( taskId );
          m_queue.AddFirst( currentTask );
        }

        this.InvokeTask();

        return true;
      }

      internal bool Dequeue( IAsyncResult taskId )
      {
        bool isFirst = false;
        bool removed = false;
        bool hasMore = false;

        lock( m_lock )
        {
          if( taskId == null || m_queue.Count <= 0 )
            return false;

          isFirst = TaskScheduler.IsSameTask( this.CurrentTask, taskId );
          removed = this.RemoveTask( taskId );
          hasMore = isFirst && ( m_queue.Count > 0 );
        }

        if( hasMore )
        {
          this.InvokeTask();
        }

        return removed;
      }

      internal bool IsCanceled( IAsyncResult taskId )
      {
        if( taskId == null )
          return true;

        lock( m_lock )
        {
          ScheduleTask scheduledTask = this.Find( taskId );

          return ( scheduledTask != null )
                   ? scheduledTask.IsCanceled
                   : true;
        }
      }

      internal bool Cancel( IAsyncResult taskId )
      {
        if( taskId == null )
          return false;

        lock( m_lock )
        {
          ScheduleTask scheduledTask = this.Find( taskId );

          if( scheduledTask != null )
          {
            scheduledTask.Cancel();

            return true;
          }

          return false;
        }
      }

      private LinkedList<ScheduleTask> m_queue = new LinkedList<ScheduleTask>();
      private IAsyncDataSourceProvider m_throttler; //null
      private Nullable<int> m_lastTickCount; //null
      private object m_lock = new object();
    }

    #endregion

    #region Private StatFunctionEqualityComparer Nested Type

    private class StatFunctionEqualityComparer : IEqualityComparer<StatFunction>
    {
      public int GetHashCode( StatFunction obj )
      {
        if( obj == null )
          return 0;

        return obj.GetEquivalenceKey();
      }

      public bool Equals( StatFunction x, StatFunction y )
      {
        return StatFunction.AreEquivalents( x, y );
      }
    }

    #endregion

    #region Private CanFilterByVisitor Nested Type

    private class CanFilterByVisitor : FilterExpressionVisitor
    {
      internal CanFilterByVisitor(
        FilterExpressionBase expression,
        Func<string, Type> getElementMemberType,
        Func<Type, IEnumerable<FilterOperator>> getSupportedFilterOperators )
        : base( expression )
      {
        if( getElementMemberType == null )
          throw new ArgumentNullException( "getElementMemberType" );

        if( getSupportedFilterOperators == null )
          throw new ArgumentNullException( "getSupportedFilterOperators" );

        m_getElementMemberType = getElementMemberType;
        m_getSupportedFilterOperators = getSupportedFilterOperators;
      }

      internal bool Result
      {
        get
        {
          if( !m_result.HasValue )
          {
            this.Visit();
          }

          return m_result.GetValueOrDefault( false );
        }
      }

      protected override void Initialize()
      {
        m_result = null;

        base.Initialize();
      }

      protected internal override void Visit( FilterExpression expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          try
          {
            Type propertyType = m_getElementMemberType.Invoke( expression.MemberName );

            m_result = m_getSupportedFilterOperators.Invoke( propertyType ).Contains( expression.FilterOperator );
          }
          catch( Exception )
          {
            m_result = false;
          }
        }
      }

      protected internal override void Visit( AndFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( OrFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( NotFilterExpression expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          this.Visit( expression.ChildExpression );
        }
      }

      private void Visit( FilterExpressionOperator expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          foreach( FilterExpressionBase childExpression in expression.ChildExpressions )
          {
            // At least one child expression isn't supported.
            if( m_result == false )
              return;

            this.Visit( childExpression );
          }
        }
      }

      private Nullable<bool> m_result; //null
      private Func<string, Type> m_getElementMemberType;
      private Func<Type, IEnumerable<FilterOperator>> m_getSupportedFilterOperators;
    }

    #endregion
  }
}
